package com.ecar.api.pay.wx;

import com.ecar.api.support.util.Redis;
import com.ecar.api.support.util.WechatXMLParser;
import com.ecar.common.exception.MyException;
import com.ecar.common.util.StringUtil;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;

import net.sf.json.JSONObject;

import org.dom4j.DocumentException;
import org.omg.CORBA.Object;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.MediaType;
import javax.xml.parsers.ParserConfigurationException;

import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;



public class WeixinPay {

	private static Logger log = LoggerFactory.getLogger(WeixinPay.class);

	/**
	 * 把对象转换成字符串
	 *
	 * @param obj
	 * @return String 转换成字符串,若对象为null,则返回空字符串.
	 */
	public static String toString(Object obj) {
		if (obj == null)
			return "";
		return obj.toString();
	}

	/**
	 * 取出一个指定长度大小的随机正整数.
	 *
	 * @param length int 设定所取出随机数的长度。length小于11
	 * @return int 返回生成的随机数。
	 */
	public static int buildRandom(int length) {
		int num = 1;
		double random = Math.random();
		if (random < 0.1) {
			random = random + 0.1;
		}
		for (int i = 0; i < length; i++) {
			num = num * 10;
		}
		return (int) ((random * num));
	}

	/**
	 * 获取unix时间，从1970-01-01 00:00:00开始的秒数
	 *
	 * @param date
	 * @return long
	 */
	public static long getUnixTime(Date date) {
		if (null == date) {
			return 0;
		}

		return date.getTime() / 1000;
	}

	/**
	 * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
	 *
	 * @noinspection WhileLoopReplaceableByForEach
	 */
	public static String createSign(Map<String, String> packageParams) {
		StringBuffer sb = toStringBuffer(packageParams);
		sb.append("&key=").append(WeixinPayConfig.partnerkey);
		String sign = MD5Util.MD5Encode(sb.toString(), WeixinPayConfig.charset);
		log.info("packge签名: {}", sign.toUpperCase());
		return sign.toUpperCase();

	}

	/**
	 * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改
	 *
	 * @param responseString API返回的XML数据字符串
	 * @return API签名是否合法
	 * @throws ParserConfigurationException
	 * @throws IOException
	 * @throws DocumentException
	 */
	public static boolean checkIsSignValidFromResponseString(String responseString)
		throws ParserConfigurationException, IOException, DocumentException {

		Map<String, String> map = WechatXMLParser.parseString(responseString);

		String signFromAPIResponse = map.get("sign");
		if ("".equals(signFromAPIResponse) || signFromAPIResponse == null) return false;
		//清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
		map.put("sign", "");
		//将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		String signForAPIResponse = createSign(map);
		return signForAPIResponse.equals(signFromAPIResponse);
	}

	/**
	 * 微信订单关闭接口
	 * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_3&index=5
	 *
	 * @param out_trade_no 商户订单号
	 * @return MAP
	 * @throws DocumentException
	 * @throws IOException
	 * @noinspection unused
	 */
	public static boolean closeOrder(String out_trade_no) throws IOException, DocumentException {
		SortedMap<String, String> packageParams = new TreeMap<>();
		packageParams.put("appid", WeixinPayConfig.appid);
		packageParams.put("mch_id", WeixinPayConfig.mch_id);
		packageParams.put("nonce_str", "" + buildRandom(4));
		packageParams.put("out_trade_no", out_trade_no);
		packageParams.put("sign", createSign(packageParams));
		//发送请求
		String xmlStr = WechatXMLParser.map2XmlString(packageParams);
		Client c = null;
		ClientResponse response = null;
		String returnEntity = null;
		try {
			c = Client.create(new DefaultClientConfig());
			response = c.resource(WeixinPayConfig.close_order_url)
						.entity(xmlStr)
						.accept(MediaType.APPLICATION_XML)
						.post(ClientResponse.class);
			returnEntity = response.getEntity(String.class);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		} finally {
			if (response != null) response.close();
			if (c != null) c.destroy();
		}
		Map<String, String> returnMap = WechatXMLParser.parseString(returnEntity);
		if ("FAIL".equals(returnMap.get("return_code"))) {
			log.info(returnMap.get("return_msg"));
			return false;
		}
		if (StringUtil.isNotEmpty(returnMap.get("err_code"))) {
			log.info(returnMap.get("err_code") + returnMap.get("err_code_des"));
			return false;
		}
		return true;
	}

	/**
	 * 微信退款单查询接口
	 * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_5&index=7
	 *
	 * @param out_refund_no 商户退款单号
	 * @return MAP
	 * @throws DocumentException
	 * @throws IOException
	 * @noinspection unused
	 */
	public Map<String, String> queryRefund(String out_refund_no) throws IOException, DocumentException {
		SortedMap<String, String> packageParams = new TreeMap<>();
		packageParams.put("appid", WeixinPayConfig.appid);
		packageParams.put("mch_id", WeixinPayConfig.mch_id);
		packageParams.put("nonce_str", "" + buildRandom(4));
		packageParams.put("out_refund_no", out_refund_no);
		packageParams.put("sign", createSign(packageParams));
		//发送请求
		String xmlStr = WechatXMLParser.map2XmlString(packageParams);
		Client c = null;
		ClientResponse response = null;
		String returnEntity = null;
		try {
			c = Client.create(new DefaultClientConfig());
			response = c.resource(WeixinPayConfig.query_refund_url)
						.entity(xmlStr)
						.accept(MediaType.APPLICATION_XML)
						.post(ClientResponse.class);
			returnEntity = response.getEntity(String.class);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (response != null) response.close();
			if (c != null) c.destroy();
		}
		return WechatXMLParser.parseString(returnEntity);
	}

	/**
	 * 调用微信退款接口
	 *
	 * @param orderNo   要退款的订单号
	 * @param refundNo  退款单号
	 * @param refundFee 要退款的金额
	 * @throws Exception
	 */
	public static boolean requestRefundApp(String orderNo, String refundNo, String refundFee) throws MyException {
		Map<String, String> queryResultMap = queryOrder(orderNo);
		if (queryResultMap == null) throw new MyException("查询微信订单失败，退款失败。");

		if (!"SUCCESS".equals(queryResultMap.get("return_code"))
			|| !"SUCCESS".equals(queryResultMap.get("result_code"))) {
			log.info(queryResultMap.get("err_code_des"));
			throw new MyException("查询微信订单失败，退款失败。");
		}
		if (!"SUCCESS".equals(queryResultMap.get("trade_state"))
			&& !"REFUND".equals(queryResultMap.get("trade_state"))) {
			log.info(queryResultMap.get("trade_state"));
			throw new MyException("微信交易不成功，无法退款");
		}
		String totalFee = queryResultMap.get("total_fee");

		//申请退款参数
		SortedMap<String, String> packageParams = new TreeMap<>();
		packageParams.put("appid", WeixinPayConfig.appid);
		packageParams.put("mch_id", WeixinPayConfig.mch_id);
		packageParams.put("nonce_str", "" + buildRandom(4));
		packageParams.put("out_trade_no", orderNo);
		packageParams.put("total_fee", totalFee);
		packageParams.put("refund_fee", refundFee);
		packageParams.put("out_refund_no", refundNo);
		packageParams.put("op_user_id", WeixinPayConfig.mch_id);
		String sign = createSign(packageParams);
		packageParams.put("sign", sign);

		//调用证书  发送加密请求
		String xmlStr = WechatXMLParser.map2XmlString(packageParams);

		log.info("微信APP退款参数：" + xmlStr);
		Map<String, String> returnMap = null;
		try {
			String returnEntity = SSLClient.doRefund(WeixinPayConfig.pay_refund_url, xmlStr);
			log.info("微信APP退款接口返回：" + returnEntity);
			returnMap = WechatXMLParser.parseString(returnEntity);
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (returnMap == null) throw new MyException("请求微信退款失败。");

		if ("FAIL".equals(returnMap.get("return_code"))) {
			String errmsg = returnMap.get("return_msg");
			log.info(errmsg);
			throw new MyException(errmsg);
		}
		if ("FAIL".equals(returnMap.get("result_code"))) {
			String errmsg = returnMap.get("err_code_des");
			log.info(returnMap.get("err_code") + errmsg);
			throw new MyException(errmsg);
		}
		return true;
	}

	/**
	 * 获取缓存的token，如果未缓存或已失效，从服务商处重新获取
	 *
	 * @return
	 */
	public static String getAccessToken() {
		String token = Redis.c.get(Redis.K.H5_TOKEN.me());
		if (StringUtil.isEmpty(token)) {
			token = requestAccessToken();
			Redis.c.setex(Redis.K.H5_TOKEN.me(), 6600, token);
		}
		return token;
	}

	/**
	 * 调用微信公众号支付退款接口
	 *
	 * @param orderNo   要退款的订单号
	 * @param refundNo  退款单号
	 * @param refundFee 要退款的金额
	 * @throws Exception
	 */
	public static boolean requestRefundH5(String orderNo, String refundNo, String refundFee) throws Exception {
		String token = getAccessToken();
		if (StringUtil.isEmpty(token)) {
			log.error("获取接口票据出错");
			throw new MyException("获取服务商接口票据出错");
		}

		JSONObject queryResultMap = queryOrderH5(orderNo, token);
		if (queryResultMap == null) throw new MyException("查询微信公众号订单失败，退款失败。");

		if (!"SUCCESS".equals(queryResultMap.getString("return_code"))
			|| !"SUCCESS".equals(queryResultMap.getString("result_code"))) {
			log.info(queryResultMap.getString("err_code_des"));
			throw new MyException("查询微信订单失败。");
		}
		if (!"SUCCESS".equals(queryResultMap.getString("trade_state"))
			&& !"REFUND".equals(queryResultMap.getString("trade_state"))) {
			log.info(queryResultMap.getString("trade_state"));
			throw new MyException("微信交易不成功，无法退款");
		}

		String total_fee = queryResultMap.getString("total_fee");
		String nonce_str = queryResultMap.getString("nonce_str");
		String fee_type = queryResultMap.getString("fee_type");

		SortedMap<String, String> packageParams = new TreeMap<>();
		packageParams.put("appid", WeixinPayConfig.FWS_appid);
		packageParams.put("mch_id", WeixinPayConfig.FWS_mchid);
		packageParams.put("nonce_str", nonce_str);
		packageParams.put("op_user_id", WeixinPayConfig.mch_id_h5);
		packageParams.put("out_refund_no", refundNo);
		packageParams.put("out_trade_no", orderNo);
		packageParams.put("refund_fee", refundFee);
		packageParams.put("refund_fee_type", fee_type);
		packageParams.put("sub_appid", WeixinPayConfig.appid_h5);
		packageParams.put("sub_mch_id", WeixinPayConfig.mch_id_h5);
		packageParams.put("total_fee", total_fee);

		String sign = queryH5Sign(packageParams, token);
		if (StringUtil.isEmpty(sign)) throw new MyException("获取微信服务商退款签名失败。");
		packageParams.put("sign", sign);

		//调用证书  发送加密请求
		String xmlStr = WechatXMLParser.map2XmlString(packageParams);
		log.info("微信公众号退款参数：{}", xmlStr);
		Map<String, String> returnMap = null;
		try {
			String returnEntity = SSLClient.doRefundH5(WeixinPayConfig.pay_refund_url, xmlStr);
			log.info("微信公众号退款接口返回：{}", returnEntity);
			returnMap = WechatXMLParser.parseString(returnEntity);
		} catch (Exception e) { e.printStackTrace(); }
		if (returnMap == null) throw new MyException("请求微信公众号退款失败。");

		if ("FAIL".equals(returnMap.get("return_code"))) {
			String errmsg = returnMap.get("return_msg");
			log.info(errmsg);
			throw new MyException(errmsg);
		}
		if ("FAIL".equals(returnMap.get("result_code"))) {
			String errmsg = returnMap.get("err_code_des");
			log.info(returnMap.get("err_code") + errmsg);
			throw new MyException(errmsg);
		}
		return true;
	}

	/**
	 * 获取接口票据   票据有效期为2小时
	 * 调用地址  http://wx.sunjee.cn/WxPayApi/Authorize/access_token?appid= appid &secret= secret
	 */
	private static String requestAccessToken() {
		Client c = null;
		ClientResponse response = null;
		try {
			//发送请求
			c = Client.create(new DefaultClientConfig());
			response = c.resource(WeixinPayConfig.FWS_token_url)
						.queryParam("appid", WeixinPayConfig.TOKEN_APPID)
						.queryParam("secret", WeixinPayConfig.TOKEN_SECRET)
						.accept(MediaType.APPLICATION_JSON)
						.get(ClientResponse.class);
			String returnEntity = response.getEntity(String.class);
			JSONObject jsonAll = JSONObject.fromObject(returnEntity);
			String strResultDate = jsonAll.getString("resultData");
			JSONObject jsonResultDate = JSONObject.fromObject(strResultDate);
			if (jsonResultDate != null && jsonResultDate.containsKey("access_token"))
				return jsonResultDate.getString("access_token");
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			if (response != null) response.close();
			if (c != null) c.destroy();
		}
		return null;
	}

	/**
	 * @noinspection WhileLoopReplaceableByForEach
	 */
	private static StringBuffer toStringBuffer(Map<String, String> packageParams) {
		StringBuffer sb = new StringBuffer();
		Set es = packageParams.entrySet();
		Iterator it = es.iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			String k = (String) entry.getKey();
			String v = (String) entry.getValue();
			if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
				sb.append(k).append("=").append(v);
				if (it.hasNext()) sb.append("&");
			}
		}
		return sb;
	}

	/**
	 * 微信公众号H5支付查询订单
	 *
	 * @param out_trade_no
	 * @return
	 * @throws IOException
	 * @throws DocumentException
	 */
	private static JSONObject queryOrderH5(String out_trade_no, String token) {
		JSONObject returnJson = null;
		Client c = null;
		ClientResponse response = null;
		try {
			JSONObject param = new JSONObject();
			param.put("out_trade_no", out_trade_no);
			//发送请求
			ClientConfig config = new DefaultClientConfig();
			config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
			c = Client.create(config);
			response = c.resource(WeixinPayConfig.FWS_query_order_url)
						.queryParam("token", token)
						.entity(param.toString())
						.accept(MediaType.APPLICATION_JSON)
						.post(ClientResponse.class);
			String returnEntity = response.getEntity(String.class);
			log.debug("微信公众号订单详情： {}", returnEntity);
			returnJson = JSONObject.fromObject(returnEntity);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (response != null) response.close();
			if (c != null) c.destroy();
		}
		return returnJson;
	}

	/**
	 * 微信公众号H5支付查询订单
	 *
	 * @param packageParams
	 * @return
	 * @throws IOException
	 * @throws DocumentException
	 */
	private static String queryH5Sign(Map<String, String> packageParams, String token) {
		Client c = null;
		ClientResponse response = null;
		try {
			JSONObject param = new JSONObject();
			param.put("url", toStringBuffer(packageParams).toString());
			log.debug("param: {}", param.toString());
			//发送请求
			ClientConfig config = new DefaultClientConfig();
			config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
			c = Client.create(config);
			response = c.resource(WeixinPayConfig.FWS_sign_url)
						.queryParam("token", token)
						.entity(param.toString())
						.accept(MediaType.APPLICATION_JSON)
						.post(ClientResponse.class);
			String returnEntity = response.getEntity(String.class);
			log.debug("h5 sign result: {}", returnEntity);
			JSONObject returnJson = JSONObject.fromObject(returnEntity);
			if (returnJson.get("resultCode").toString().equals("0"))
				return (String) returnJson.get("resultData");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (response != null) response.close();
			if (c != null) c.destroy();
		}
		return null;
	}

	/**
	 * 微信订单查询接口
	 * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_2&index=4
	 *
	 * @param out_trade_no 商户订单号
	 * @return MAP
	 * @throws DocumentException
	 * @throws IOException
	 */
	private static Map<String, String> queryOrder(String out_trade_no) {
		Map<String, String> returnMap = null;
		Client c = null;
		ClientResponse response = null;
		try {
			SortedMap<String, String> packageParams = new TreeMap<>();
			packageParams.put("appid", WeixinPayConfig.appid);
			packageParams.put("mch_id", WeixinPayConfig.mch_id);
			packageParams.put("nonce_str", "" + WeixinPay.buildRandom(4));
			packageParams.put("out_trade_no", out_trade_no);
			String sign = WeixinPay.createSign(packageParams);
			packageParams.put("sign", sign);
			//发送请求
			String xmlStr = WechatXMLParser.map2XmlString(packageParams);
			c = Client.create(new DefaultClientConfig());
			response = c.resource(WeixinPayConfig.query_order_url)
						.entity(xmlStr)
						.accept(MediaType.APPLICATION_XML)
						.post(ClientResponse.class);
			String returnEntity = response.getEntity(String.class);
			log.debug("微信APP订单详情： {}", returnEntity);
			returnMap = WechatXMLParser.parseString(returnEntity);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (response != null) response.close();
			if (c != null) c.destroy();
		}
		return returnMap;
	}
}










